/**
 * =================================================================
 * 版权所有 2011-2013 深圳市泰海网络科技服务有限公司，并保留所有权利
 * -----------------------------------------------------------------
 * 这不是一个自由软件！您不能在任何未经允许的前提下对程序代码进行修改和使用；
 * 不允许对程序代码以任何形式任何目的的再发布
 * =================================================================
 */
package com.sfpay.api;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.sfpay.api.internal.parser.json.ObjectJsonParser;
import com.sfpay.api.internal.parser.xml.ObjectXmlParser;
import com.sfpay.api.internal.util.RequestParametersHolder;
import com.sfpay.api.internal.util.SfpayHashMap;
import com.sfpay.api.internal.util.SfpayLogger;
import com.sfpay.api.internal.util.SfpayUtils;
import com.sfpay.api.internal.util.WebUtils;

/**
 * 类说明：基于REST的客户端。<br>
 * 
 * <p>
 * 详细描述：<br>
 * 
 * </p>
 * <pre>
 * ——————————————————————————————————————————————————————————————————
 * |		修改人		|		修改时间			|		修改原因
 * ——————————————————————————————————————————————————————————————————
 * |	zengxx 曾宪新	|		2013-8-18		|	
 * ——————————————————————————————————————————————————————————————————
 * </pre>
 * 
 * @author zengxx 曾宪新(Xavier.zeng)
 * 
 * CreateDate: 2013-8-18
 */
public class DefaultSfpayClient implements SfpayClient {

	/**
	 * 应用Key
	 */
	private static final String APP_KEY = "appKey";
	
	/**
	 * 数据回传格式  XML/JSON
	 */
	private static final String FORMAT = "format";
	
	/**
	 * 调用方法名称
	 */
	private static final String METHOD = "method";
	
	/**
	 * 时间戳
	 */
	private static final String TIMESTAMP = "timestamp";
	
	/**
	 * 版本号
	 */
	private static final String VERSION = "v";
	
	/**
	 * 加密/签名后数据
	 */
	private static final String SIGN = "sign";
	
	/**
	 * 加密/签名类型
	 */
	private static final String SIGN_TYPE = "signType";
	
	/**
	 * 参数编码
	 */
	private static final String CHARSET_TYPE = "inputCharset";
	
	/**
	 * 商家使用版本号
	 */
	private static final String PARTNER_ID = "partnerId";
	
	/**
	 * 会话
	 */
	private static final String SESSION = "session";
	
	/**
	 * 请求服务地址
	 */
	private String serverUrl;
	
	/**
	 * 应用Key
	 */
	private String appKey;
	
	/**
	 * 加密/签名密钥
	 */
	private String appSecret;
	
	/**
	 * 应答数据格式
	 */
	private String format = Constants.FORMAT_XML;
	
	/**
	 * 签名方式
	 */
	private String signType = Constants.SIGN_METHOD_RSA;
	
	/**
	 * 连接超时时间
	 */
	private int connectTimeout = 3000;//3秒
	
	/**
	 * 请求超时时间
	 */
	private int readTimeout = 15000;//15秒
	
	/**
	 * 参数是否校验
	 */
	private boolean needCheckRequest = true;
	
	/**
	 * 返回参数是否转换
	 */
	private boolean needEnableParser = true;
	
	public DefaultSfpayClient(String serverUrl, String appKey, String appSecret) {
		this.appKey = appKey;
		this.appSecret = appSecret;
		this.serverUrl = serverUrl;
	}

	public DefaultSfpayClient(String serverUrl, String appKey, String appSecret, String format) {
		this(serverUrl, appKey, appSecret);
		this.format = format;
	}

	public DefaultSfpayClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout, int readTimeout) {
		this(serverUrl, appKey, appSecret, format);
		this.connectTimeout = connectTimeout;
		this.readTimeout = readTimeout;
	}

	public DefaultSfpayClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout, int readTimeout, String signMethod) {
		this(serverUrl, appKey, appSecret, format, connectTimeout, readTimeout);
		this.signType = signMethod;
	}
	
	@Override
	public <T extends SfpayResponse> T execute(SfpayRequest<T> request)
			throws ApiException {
		return execute(request, null);
	}

	@Override
	public <T extends SfpayResponse> T execute(SfpayRequest<T> request,
			String session) throws ApiException {
		SfpayParser<T> parser = null;
		if (this.needEnableParser) {
			if (Constants.FORMAT_XML.equals(this.format)) {
				parser = new ObjectXmlParser<T>(request.getResponseClass());
			} else if (Constants.FORMAT_JSON.equals(this.format)) {
				parser = new ObjectJsonParser<T>(request.getResponseClass());
			}
		}
		return _execute(request, parser, session);
	}

	private <T extends SfpayResponse> T _execute(SfpayRequest<T> request, SfpayParser<T> parser, String session) throws ApiException {
		if (this.needCheckRequest) {
			try {
				request.check();
			} catch (ApiRuleException e) {
				T localResponse = null;
				try {
					localResponse = request.getResponseClass().newInstance();
				} catch (InstantiationException e2) {
					throw new ApiException(e2);
				} catch (IllegalAccessException e3) {
					throw new ApiException(e3);
				}
				localResponse.setErrorCode(e.getErrCode());
				localResponse.setMsg(e.getErrMsg());
				return localResponse;
			}
		}
		
		Map<String, Object> rt = doPost(request, session);
		if (null == rt) {
			return null;
		}
		
		T tRsp = null;
		if (this.needEnableParser) {
			try {
				tRsp = parser.parse((String) rt.get("rsp"));
				tRsp.setBody((String) rt.get("rsp"));
			} catch (RuntimeException e) {
				SfpayLogger.logBizError((String) rt.get("rsp"));
				throw e;
			}
		} else {
			try {
				tRsp = request.getResponseClass().newInstance();
				tRsp.setBody((String) rt.get("rsp"));
			} catch (Exception e) {
			}
		}

		tRsp.setParams((SfpayHashMap) rt.get("textParams"));
		if (!tRsp.isSuccess()) {
			SfpayLogger.logErrorScene(rt, tRsp, appSecret);
		}
		return tRsp;
	}
	
	public <T extends SfpayResponse> Map<String, Object> doPost(SfpayRequest<T> request, String session) throws ApiException {
		Map<String, Object> result = new HashMap<String, Object>();
		RequestParametersHolder requestHolder = new RequestParametersHolder();
		SfpayHashMap appParams = new SfpayHashMap(request.getTextParams());
		requestHolder.setApplicationParams(appParams);

		// 添加协议级请求参数
		SfpayHashMap protocalMustParams = new SfpayHashMap();
		protocalMustParams.put(METHOD, request.getApiMethodName());
		protocalMustParams.put(VERSION, "2.0");
		protocalMustParams.put(APP_KEY, appKey);
		protocalMustParams.put(CHARSET_TYPE, Constants.CHARSET_UTF8);
		Long timestamp = request.getTimestamp();// 允许用户设置时间戳
		if (timestamp == null) {
			timestamp = System.currentTimeMillis();
		}

		protocalMustParams.put(TIMESTAMP, new Date(timestamp));// 因为目前只支持时间字符串，所以暂时用Date格式
		requestHolder.setProtocalMustParams(protocalMustParams);

		SfpayHashMap protocalOptParams = new SfpayHashMap();
		protocalOptParams.put(FORMAT, format);
		protocalOptParams.put(SIGN_TYPE, signType);
		protocalOptParams.put(SESSION, session);
		protocalOptParams.put(PARTNER_ID, Constants.SDK_VERSION);
		requestHolder.setProtocalOptParams(protocalOptParams);

		// 添加签名参数
		try {
			protocalMustParams.put(SIGN, SfpayUtils.signSfpRequestNew(requestHolder, appSecret, signType));
		} catch (IOException e) {
			throw new ApiException(e);
		}
		
		StringBuffer urlSb = new StringBuffer(serverUrl);
		try {
			String sysMustQuery = WebUtils.buildQuery(requestHolder.getProtocalMustParams(), Constants.CHARSET_UTF8);
			String sysOptQuery = WebUtils.buildQuery(requestHolder.getProtocalOptParams(), Constants.CHARSET_UTF8);

			urlSb.append("?");
			urlSb.append(sysMustQuery);
			if (sysOptQuery != null & sysOptQuery.length() > 0) {
				urlSb.append("&");
				urlSb.append(sysOptQuery);
			}
		} catch (IOException e) {
			throw new ApiException(e);
		}

		String rsp = null;
		try {
			// 是否需要上传文件
			if (request instanceof SfpayUploadRequest) {
				SfpayUploadRequest<T> uRequest = (SfpayUploadRequest<T>) request;
				Map<String, FileItem> fileParams = SfpayUtils.cleanupMap(uRequest.getFileParams());
				rsp = WebUtils.doPost(urlSb.toString(), appParams, fileParams, Constants.CHARSET_UTF8, connectTimeout, readTimeout,request.getHeaderMap());
			} else {
				rsp = WebUtils.doPost(urlSb.toString(), appParams, Constants.CHARSET_UTF8, connectTimeout, readTimeout,request.getHeaderMap());
			}
		} catch (IOException e) {
			throw new ApiException(e);
		}
		result.put("rsp", rsp);
		result.put("textParams", appParams);
		result.put("protocalMustParams", protocalMustParams);
		result.put("protocalOptParams", protocalOptParams);
		result.put("url", urlSb.toString());
		return result;
	}
	
	public void setNeedCheckRequest(boolean needCheckRequest) {
		this.needCheckRequest = needCheckRequest;
	}

	public void setNeedEnableParser(boolean needEnableParser) {
		this.needEnableParser = needEnableParser;
	}

	public void setNeedEnableLogger(boolean needEnableLogger) {
		SfpayLogger.setNeedEnableLogger(needEnableLogger);
	}
}
